package okhttp3.internal.huc;

import static okhttp3.internal.Util.UTC;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.SocketPermission;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.Permission;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import okhttp3.Cache;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Dispatcher;
import okhttp3.Handshake;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttp;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.JavaNetHeaders;
import okhttp3.internal.URLFilter;
import okhttp3.internal.http.DatesKt;
import okhttp3.internal.http.HttpHeaders;
import okhttp3.internal.http.HttpMethod;
import okhttp3.internal.http.StatusLine;
import okhttp3.internal.platform.Platform;
import okio.Buffer;

/**
 * <p>Title: OkHttpURLConnection </p>
 * <p>Description:  </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2023/11/21 16:34
 */
public final class OkHttpURLConnection extends HttpURLConnection implements Callback {
    public static final String SELECTED_PROTOCOL = Platform.get().getPrefix() + "-Selected-Protocol";
    public static final String RESPONSE_SOURCE = Platform.get().getPrefix() + "-Response-Source";
    private static final Set<String> METHODS = new LinkedHashSet(Arrays.asList("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "PATCH"));
    OkHttpClient client;
    private final NetworkInterceptor networkInterceptor;
    private Headers.Builder requestHeaders;
    private boolean executed;
    Call call;
    URLFilter urlFilter;
    private Headers responseHeaders;
    private long fixedContentLength;
    private final Object lock;
    private Response response;
    private Throwable callFailure;
    Response networkResponse;
    boolean connectPending;
    Proxy proxy;
    Handshake handshake;

    public OkHttpURLConnection(URL url, OkHttpClient client) {
        super(url);
        this.networkInterceptor = new NetworkInterceptor();
        this.requestHeaders = new Headers.Builder();
        this.fixedContentLength = -1L;
        this.lock = new Object();
        this.connectPending = true;
        this.client = client;
    }

    public OkHttpURLConnection(URL url, OkHttpClient client, URLFilter urlFilter) {
        this(url, client);
        this.urlFilter = urlFilter;
    }

    public void connect() throws IOException {
        if (!this.executed) {
            Call call = this.buildCall();
            this.executed = true;
            call.enqueue(this);
            synchronized (this.lock) {
                try {
                    while (this.connectPending && this.response == null && this.callFailure == null) {
                        this.lock.wait();
                    }

                    if (this.callFailure != null) {
                        throw propagate(this.callFailure);
                    }
                } catch (InterruptedException var5) {
                    Thread.currentThread().interrupt();
                    throw new InterruptedIOException();
                }

            }
        }
    }

    public void disconnect() {
        if (this.call != null) {
            this.networkInterceptor.proceed();
            this.call.cancel();
        }
    }

    public InputStream getErrorStream() {
        try {
            Response response = this.getResponse(true);
            ResponseBody responseBody = response.body();
            if (responseBody == null) {
                return null;
            }
            return HttpHeaders.hasBody(response) && response.code() >= 400 ? responseBody.byteStream() : null;
        } catch (IOException var2) {
            return null;
        }
    }

    private Headers getHeaders() throws IOException {
        if (this.responseHeaders == null) {
            Response response = this.getResponse(true);
            Headers headers = response.headers();
            this.responseHeaders = headers.newBuilder().add(SELECTED_PROTOCOL, response.protocol().toString()).add(RESPONSE_SOURCE, responseSourceHeader(response)).build();
        }

        return this.responseHeaders;
    }

    private static String responseSourceHeader(Response response) {
        if (response.networkResponse() == null) {
            return response.cacheResponse() == null ? "NONE" : "CACHE " + response.code();
        } else {
            return response.cacheResponse() == null ? "NETWORK " + response.code() : "CONDITIONAL_CACHE " + response.networkResponse().code();
        }
    }

    public String getHeaderField(int position) {
        try {
            Headers headers = this.getHeaders();
            return position >= 0 && position < headers.size() ? headers.value(position) : null;
        } catch (IOException var3) {
            return null;
        }
    }

    public String getHeaderField(String fieldName) {
        try {
            return fieldName == null ? StatusLine.Companion.get(this.getResponse(true)).toString() : this.getHeaders().get(fieldName);
        } catch (IOException var3) {
            return null;
        }
    }

    public String getHeaderFieldKey(int position) {
        try {
            Headers headers = this.getHeaders();
            return position >= 0 && position < headers.size() ? headers.name(position) : null;
        } catch (IOException var3) {
            return null;
        }
    }

    public Map<String, List<String>> getHeaderFields() {
        try {
            return JavaNetHeaders.toMultimap(this.getHeaders(), StatusLine.Companion.get(this.getResponse(true)).toString());
        } catch (IOException var2) {
            return Collections.emptyMap();
        }
    }

    public Map<String, List<String>> getRequestProperties() {
        if (this.connected) {
            throw new IllegalStateException("Cannot access request header fields after connection is set");
        } else {
            return JavaNetHeaders.toMultimap(this.requestHeaders.build(), (String) null);
        }
    }

    public InputStream getInputStream() throws IOException {
        if (!this.doInput) {
            throw new ProtocolException("This protocol does not support input");
        } else {
            Response response = this.getResponse(false);
            if (response.code() >= 400) {
                throw new FileNotFoundException(this.url.toString());
            } else {
                ResponseBody body = response.body();
                if (body == null) {
                    return null;
                }
                return body.byteStream();
            }
        }
    }

    public OutputStream getOutputStream() throws IOException {
        OutputStreamRequestBody requestBody = (OutputStreamRequestBody) this.buildCall().request().body();
        if (requestBody == null) {
            throw new ProtocolException("method does not support a request body: " + this.method);
        } else {
            if (requestBody instanceof StreamedRequestBody) {
                this.connect();
                this.networkInterceptor.proceed();
            }

            if (requestBody.isClosed()) {
                throw new ProtocolException("cannot write request body after response has been read");
            } else {
                return requestBody.outputStream();
            }
        }
    }

    public Permission getPermission() throws IOException {
        URL url = this.getURL();
        String hostname = url.getHost();
        int hostPort = url.getPort() != -1 ? url.getPort() : HttpUrl.defaultPort(url.getProtocol());
        if (this.usingProxy()) {
            InetSocketAddress proxyAddress = (InetSocketAddress) this.client.proxy().address();
            hostname = proxyAddress.getHostName();
            hostPort = proxyAddress.getPort();
        }

        return new SocketPermission(hostname + ":" + hostPort, "connect, resolve");
    }

    public String getRequestProperty(String field) {
        return field == null ? null : this.requestHeaders.get(field);
    }

    public void setConnectTimeout(int timeoutMillis) {
        this.client = this.client.newBuilder().connectTimeout((long) timeoutMillis, TimeUnit.MILLISECONDS).build();
    }

    public void setInstanceFollowRedirects(boolean followRedirects) {
        this.client = this.client.newBuilder().followRedirects(followRedirects).build();
    }

    public boolean getInstanceFollowRedirects() {
        return this.client.followRedirects();
    }

    public int getConnectTimeout() {
        return this.client.connectTimeoutMillis();
    }

    public void setReadTimeout(int timeoutMillis) {
        this.client = this.client.newBuilder().readTimeout((long) timeoutMillis, TimeUnit.MILLISECONDS).build();
    }

    public int getReadTimeout() {
        return this.client.readTimeoutMillis();
    }

    private Call buildCall() throws IOException {
        if (this.call != null) {
            return this.call;
        } else {
            this.connected = true;
            if (this.doOutput) {
                if (this.method.equals("GET")) {
                    this.method = "POST";
                } else if (!HttpMethod.permitsRequestBody(this.method)) {
                    throw new ProtocolException(this.method + " does not support writing");
                }
            }

            if (requestHeaders != null && this.requestHeaders.get("User-Agent") == null) {
                this.requestHeaders.add("User-Agent", this.defaultUserAgent());
            }

            OutputStreamRequestBody requestBody = null;
            if (HttpMethod.permitsRequestBody(this.method)) {
                String contentType = this.requestHeaders.get("Content-Type");
                if (contentType == null) {
                    contentType = "application/x-www-form-urlencoded";
                    this.requestHeaders.add("Content-Type", contentType);
                }

                boolean stream = this.fixedContentLength != -1L || this.chunkLength > 0;
                long contentLength = -1L;
                String contentLengthString = this.requestHeaders.get("Content-Length");
                if (this.fixedContentLength != -1L) {
                    contentLength = this.fixedContentLength;
                } else if (contentLengthString != null) {
                    contentLength = Long.parseLong(contentLengthString);
                }

                requestBody = stream ? new StreamedRequestBody(contentLength) : new BufferedRequestBody(contentLength);
                ((OutputStreamRequestBody) requestBody).timeout().timeout((long) this.client.writeTimeoutMillis(), TimeUnit.MILLISECONDS);
            }

            HttpUrl url;
            try {
                url = HttpUrl.get(this.getURL().toString());
            } catch (IllegalArgumentException var7) {
                if (var7.getMessage().startsWith("Invalid URL host")) {
                    UnknownHostException unknownHost = new UnknownHostException();
                    unknownHost.initCause(var7);
                    throw unknownHost;
                }

                MalformedURLException malformedUrl = new MalformedURLException();
                malformedUrl.initCause(var7);
                throw malformedUrl;
            }

            Request request = (new Request.Builder()).url(url).headers(this.requestHeaders.build()).method(this.method, (RequestBody) requestBody).build();
            if (this.urlFilter != null) {
                this.urlFilter.checkURLPermitted(request.url().url());
            }

            OkHttpClient.Builder clientBuilder = this.client.newBuilder();
            clientBuilder.interceptors().clear();
            clientBuilder.interceptors().add(UnexpectedException.INTERCEPTOR);
            clientBuilder.networkInterceptors().clear();
            clientBuilder.networkInterceptors().add(this.networkInterceptor);
            clientBuilder.dispatcher(new Dispatcher(this.client.dispatcher().executorService()));
            if (!this.getUseCaches()) {
                clientBuilder.cache((Cache) null);
            }

            return this.call = clientBuilder.build().newCall(request);
        }
    }

    private String defaultUserAgent() {
        String agent = System.getProperty("http.agent");
        return agent != null ? toHumanReadableAscii(agent) : OkHttp.VERSION;
    }

    private static String toHumanReadableAscii(String s) {
        int i = 0;

        int c;
        for (int length = s.length(); i < length; i += Character.charCount(c)) {
            c = s.codePointAt(i);
            if (c <= 31 || c >= 127) {
                Buffer buffer = new Buffer();
                buffer.writeUtf8(s, 0, i);
                buffer.writeUtf8CodePoint(63);

                for (int j = i + Character.charCount(c); j < length; j += Character.charCount(c)) {
                    c = s.codePointAt(j);
                    buffer.writeUtf8CodePoint(c > 31 && c < 127 ? c : 63);
                }

                return buffer.readUtf8();
            }
        }

        return s;
    }

    private Response getResponse(boolean networkResponseOnError) throws IOException {
        synchronized (this.lock) {
            if (this.response != null) {
                return this.response;
            }

            if (this.callFailure != null) {
                if (networkResponseOnError && this.networkResponse != null) {
                    return this.networkResponse;
                }

                throw propagate(this.callFailure);
            }
        }

        Call call = this.buildCall();
        this.networkInterceptor.proceed();
        OutputStreamRequestBody requestBody = (OutputStreamRequestBody) call.request().body();
        if (requestBody != null) {
            requestBody.outputStream().close();
        }

        if (this.executed) {
            synchronized (this.lock) {
                try {
                    while (this.response == null && this.callFailure == null) {
                        this.lock.wait();
                    }
                } catch (InterruptedException var10) {
                    Thread.currentThread().interrupt();
                    throw new InterruptedIOException();
                }
            }
        } else {
            this.executed = true;

            try {
                this.onResponse(call, call.execute());
            } catch (IOException var8) {
                this.onFailure(call, var8);
            }
        }

        synchronized (this.lock) {
            if (this.callFailure != null) {
                throw propagate(this.callFailure);
            }

            if (this.response != null) {
                return this.response;
            }
        }

        throw new AssertionError();
    }

    public boolean usingProxy() {
        if (this.proxy != null) {
            return true;
        } else {
            Proxy clientProxy = this.client.proxy();
            return clientProxy != null && clientProxy.type() != Proxy.Type.DIRECT;
        }
    }

    public String getResponseMessage() throws IOException {
        return this.getResponse(true).message();
    }

    public int getResponseCode() throws IOException {
        return this.getResponse(true).code();
    }

    public void setRequestProperty(String field, String newValue) {
        if (this.connected) {
            throw new IllegalStateException("Cannot set request property after connection is made");
        } else if (field == null) {
            throw new NullPointerException("field == null");
        } else if (newValue == null) {
            Platform.get().log("Ignoring header " + field + " because its value was null.", 5, (Throwable) null);
        } else {
            this.requestHeaders.set(field, newValue);
        }
    }

    public void setIfModifiedSince(long newValue) {
        super.setIfModifiedSince(newValue);
        if (this.ifModifiedSince != 0L) {
//            this.requestHeaders.set("If-Modified-Since", HttpDate.format(new Date(this.ifModifiedSince)));
            try {
                //noinspection KotlinInternalInJava
                this.requestHeaders.set("If-Modified-Since", DatesKt.toHttpDateString(new Date(this.ifModifiedSince)));
            } catch (Exception e) {
                final ThreadLocal<DateFormat> STANDARD_DATE_FORMAT =
                        new ThreadLocal<DateFormat>() {
                            @Override
                            protected DateFormat initialValue() {
                                // Date format specified by RFC 7231 section 7.1.1.1.
                                DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
                                rfc1123.setLenient(false);
                                rfc1123.setTimeZone(UTC);
                                return rfc1123;
                            }
                        };
                DateFormat dateFormat = STANDARD_DATE_FORMAT.get();
                if (dateFormat == null) {
                    this.requestHeaders.removeAll("If-Modified-Since");
                    return;
                }
                this.requestHeaders.set("If-Modified-Since", dateFormat.format(new Date(this.ifModifiedSince)));
            }
        } else {
            this.requestHeaders.removeAll("If-Modified-Since");
        }

    }

    public void addRequestProperty(String field, String value) {
        if (this.connected) {
            throw new IllegalStateException("Cannot add request property after connection is made");
        } else if (field == null) {
            throw new NullPointerException("field == null");
        } else if (value == null) {
            Platform.get().log("Ignoring header " + field + " because its value was null.", 5, (Throwable) null);
        } else {
            this.requestHeaders.add(field, value);
        }
    }

    public void setRequestMethod(String method) throws ProtocolException {
        if (!METHODS.contains(method)) {
            throw new ProtocolException("Expected one of " + METHODS + " but was " + method);
        } else {
            this.method = method;
        }
    }

    public void setFixedLengthStreamingMode(int contentLength) {
        this.setFixedLengthStreamingMode((long) contentLength);
    }

    public void setFixedLengthStreamingMode(long contentLength) {
        if (super.connected) {
            throw new IllegalStateException("Already connected");
        } else if (this.chunkLength > 0) {
            throw new IllegalStateException("Already in chunked mode");
        } else if (contentLength < 0L) {
            throw new IllegalArgumentException("contentLength < 0");
        } else {
            this.fixedContentLength = contentLength;
            super.fixedContentLength = (int) Math.min(contentLength, 2147483647L);
        }
    }

    public void onFailure(Call call, IOException e) {
        synchronized (this.lock) {
            this.callFailure = (Throwable) (e instanceof UnexpectedException ? e.getCause() : e);
            this.lock.notifyAll();
        }
    }

    public void onResponse(Call call, Response response) {
        synchronized (this.lock) {
            this.response = response;
            this.handshake = response.handshake();
            this.url = response.request().url().url();
            this.lock.notifyAll();
        }
    }

    private static IOException propagate(Throwable throwable) throws IOException {
        if (throwable instanceof IOException) {
            throw (IOException) throwable;
        } else if (throwable instanceof Error) {
            throw (Error) throwable;
        } else if (throwable instanceof RuntimeException) {
            throw (RuntimeException) throwable;
        } else {
            throw new AssertionError();
        }
    }

    final class NetworkInterceptor implements Interceptor {
        private boolean proceed;

        NetworkInterceptor() {
        }

        public void proceed() {
            synchronized (OkHttpURLConnection.this.lock) {
                this.proceed = true;
                OkHttpURLConnection.this.lock.notifyAll();
            }
        }

        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            if (OkHttpURLConnection.this.urlFilter != null) {
                OkHttpURLConnection.this.urlFilter.checkURLPermitted(request.url().url());
            }

            synchronized (OkHttpURLConnection.this.lock) {
                OkHttpURLConnection.this.connectPending = false;
                OkHttpURLConnection.this.proxy = chain.connection().route().proxy();
                OkHttpURLConnection.this.handshake = chain.connection().handshake();
                OkHttpURLConnection.this.lock.notifyAll();

                try {
                    while (!this.proceed) {
                        OkHttpURLConnection.this.lock.wait();
                    }
                } catch (InterruptedException var8) {
                    Thread.currentThread().interrupt();
                    throw new InterruptedIOException();
                }
            }

            if (request.body() instanceof OutputStreamRequestBody) {
                OutputStreamRequestBody requestBody = (OutputStreamRequestBody) request.body();
                request = requestBody.prepareToSendRequest(request);
            }

            Response response = chain.proceed(request);
            synchronized (OkHttpURLConnection.this.lock) {
                OkHttpURLConnection.this.networkResponse = response;
                OkHttpURLConnection.this.url = response.request().url().url();
                return response;
            }
        }
    }

    static final class UnexpectedException extends IOException {
        static final Interceptor INTERCEPTOR = new Interceptor() {
            public Response intercept(Chain chain) throws IOException {
                try {
                    return chain.proceed(chain.request());
                } catch (RuntimeException | Error var3) {
                    throw new UnexpectedException(var3);
                }
            }
        };

        UnexpectedException(Throwable cause) {
            super(cause);
        }
    }
}